package com.platform.core.service.impl;

import com.platform.comm.service.impl.CommonBizImpl;
import com.platform.core.repository.SysFunctionRepository;
import com.platform.core.repository.SysRoleFunctionRepository;
import com.querydsl.core.BooleanBuilder;
import com.platform.comm.dto.DtoMapper;
import com.platform.dto.FunctionQo;
import com.platform.core.entity.QSysFunction;
import com.platform.core.entity.QSysRoleFunction;
import com.platform.core.entity.SysFunction;
import com.platform.core.entity.SysRoleFunction;
import com.platform.core.service.SysRoleFunctionService;
import com.platform.enums.FunctionStatus;
import io.jsonwebtoken.lang.Assert;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.querydsl.QSort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.MultiValueMap;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import static com.platform.comm.constants.GlobalConstant.BASE_REDIS_CACHE_PREFIX;
import static com.platform.core.service.BaseEntityBiz.ROUTE_SEPARATOR;

/**
 * Description: 角色菜单-Service实现类
 *
 * @author libin Created on 2019/7/22
 **/
@Service
public class SysRoleFunctionServiceImpl extends CommonBizImpl<SysRoleFunctionRepository, SysRoleFunction, String> implements SysRoleFunctionService {

    private final QSysRoleFunction qSysRoleFunction = QSysRoleFunction.sysRoleFunction;
    private final QSysFunction qSysFunction = QSysFunction.sysFunction;

    private final SysFunctionRepository sysFunctionRepository;
    private final DtoMapper dtoMapper;

    public SysRoleFunctionServiceImpl(SysRoleFunctionRepository baseRepository,
                                      SysFunctionRepository sysFunctionRepository, DtoMapper dtoMapper) {
        super(baseRepository);
        this.sysFunctionRepository = sysFunctionRepository;
        this.dtoMapper = dtoMapper;
    }

    @CacheEvict(value = {BASE_REDIS_CACHE_PREFIX + "roleFunctions", BASE_REDIS_CACHE_PREFIX + "roleAdminFunctions"},
            allEntries = true)
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createRelated(String roleId, List<String> funIds) {
        funIds.forEach(funId -> {
            SysRoleFunction entity = new SysRoleFunction();
            entity.setRoleId(roleId);
            entity.setFunctionId(funId);
            // 查询子目录并授权
            Iterable<SysFunction> iterable = sysFunctionRepository.findAll(qSysFunction.parentId.eq(funId)
                    .and(qSysFunction.delFlag.eq(FunctionStatus.Y)));
            List<String> childrenIds = StreamSupport.stream(iterable.spliterator(), true)
                    .map(SysFunction::getId).collect(Collectors.toList());
            createRelated(roleId, childrenIds);
            saveEntity(entity);
        });
    }

    @CacheEvict(value = {BASE_REDIS_CACHE_PREFIX + "roleFunctions", BASE_REDIS_CACHE_PREFIX + "roleAdminFunctions"},
            allEntries = true)
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void removeRelated(String roleId, List<String> funIds) {
        repository.deleteInBatch(repository.findAll(qSysRoleFunction.roleId.eq(roleId)
                .and(qSysRoleFunction.functionId.in(funIds))));
        funIds.parallelStream().forEach(funId -> {
            // 查询子目录并删除授权
            Iterable<SysFunction> iterable = sysFunctionRepository.findAll(qSysFunction.parentId.eq(funId)
                    .and(qSysFunction.delFlag.eq(FunctionStatus.Y)));
            List<String> childrenIds = StreamSupport.stream(iterable.spliterator(), true)
                    .map(SysFunction::getId).collect(Collectors.toList());
            removeRelated(roleId, childrenIds);
        });
    }

    @CacheEvict(value = {BASE_REDIS_CACHE_PREFIX + "roleFunctions", BASE_REDIS_CACHE_PREFIX + "roleAdminFunctions"},
            allEntries = true)
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void removeByRoleId(String roleId) {
        Iterable<SysRoleFunction> list = repository.findAll(qSysRoleFunction.roleId.eq(roleId));
        repository.deleteInBatch(list);
    }

    @CacheEvict(value = {BASE_REDIS_CACHE_PREFIX + "roleFunctions", BASE_REDIS_CACHE_PREFIX + "roleAdminFunctions"},
            allEntries = true)
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void removeByFuncId(String funId) {
        repository.deleteInBatch(repository.findAll(qSysRoleFunction.functionId.eq(funId)));
    }

    @Transactional(readOnly = true)
    @Cacheable(value = BASE_REDIS_CACHE_PREFIX + "roleFunctions", unless = "#result.size() == 0",
            key = "T(com.platform.core.security.auth.AuthenticationFacade).userCacheKey")
    @Override
    public Set<FunctionQo> findFunctionByRole(List<String> roleIds) {

        return sysFunctionRepository.findAll(qSysRoleFunction.roleId.in(roleIds),
                jpqlQuery -> jpqlQuery.leftJoin(qSysRoleFunction).on(qSysFunction.id.eq(qSysRoleFunction.functionId)),
                qSysFunction.route).parallelStream()
                .map(tuple -> tuple.get(qSysFunction.route))
                .filter(StringUtils::isNotBlank)
                .map(route -> StringUtils.split(route, ROUTE_SEPARATOR))
                .flatMap(Arrays::stream)
                .distinct()
                .map(funId -> sysFunctionRepository.findOne(qSysFunction.id.eq(funId).and(qSysFunction.delFlag.eq(FunctionStatus.Y))))
                .filter(x -> {return x.isPresent();})
                .map(Optional::get)
                .map(sysFunction -> dtoMapper.map(sysFunction, FunctionQo.class))
                .collect(Collectors.toSet());
    }

    @Cacheable(value = BASE_REDIS_CACHE_PREFIX + "roleAdminFunctions", unless = "#result.size() == 0",
            key = "'functionsByAdmin'")
    @Override
    public Set<FunctionQo> findFunctionByAdmin() {
        // 管理员
        return StreamSupport.stream(
                sysFunctionRepository.findAll(qSysFunction.parentId.isNotNull()
                        .and(qSysFunction.delFlag.eq(FunctionStatus.Y))).spliterator(),
                true)
                .map(sysFunction -> dtoMapper.map(sysFunction, FunctionQo.class))
                .collect(Collectors.toSet());
    }

    @Override
    public List<String> findFunctionsByRole(String roleId) {
        Assert.notNull(roleId, "角色ID不能为空");
        return StreamSupport.stream(repository.findAll(qSysRoleFunction.roleId.eq(roleId)).spliterator(), true)
                .map(SysRoleFunction::getFunctionId).collect(Collectors.toList());
    }

    @Override
    public Page<FunctionQo> queryFunctions(Pageable pageable, MultiValueMap<String, String> parameters) {
        String functionId = parameters.getFirst("functionId");
        String roleId = parameters.getFirst("roleId");
        String functionName = parameters.getFirst("name");
        BooleanBuilder builder = new BooleanBuilder(qSysFunction.delFlag.eq(FunctionStatus.Y));
        if (StringUtils.isNotBlank(functionId)) {
            builder.and(qSysFunction.parentId.eq(functionId));
        }
        if (StringUtils.isNotBlank(functionName)) {
            builder.and(qSysFunction.name.like("%" + functionName + "%"));
        }
        if (pageable.getSort().isUnsorted()) {
            pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), QSort.by(qSysFunction.sortNum.asc()));
        }
        Page<SysFunction> page = sysFunctionRepository.findAll(builder, pageable);
        List<String> list = findFunctionsByRole(roleId);

        return page.map(sysFunction -> {
            FunctionQo functionQo = dtoMapper.map(sysFunction, FunctionQo.class);
            functionQo.setAuthorized(list.contains(functionQo.getId()));
            return functionQo;
        });
    }

    @CacheEvict(value = {BASE_REDIS_CACHE_PREFIX + "roleFunctions", BASE_REDIS_CACHE_PREFIX + "roleAdminFunctions"},
            allEntries = true)
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createOrRemoveRelated(String roleId, List<String> funIds) {
        // 删除该角色关联的所有权限
        repository.deleteInBatch(repository.findAll(qSysRoleFunction.roleId.eq(roleId)));
        // 添加关联
        funIds.forEach(funId -> {
            SysRoleFunction entity = new SysRoleFunction();
            entity.setRoleId(roleId);
            entity.setFunctionId(funId);
            saveEntity(entity);
        });
    }
}
